package component

import (
	"common/biz"
	"common/logs"
	"common/utils"
	"core/service"
	"fmt"
	"framework/msError"
	"framework/remote"
	"game/component/base"
	"game/component/gameCard"
	"game/component/proto"
	"game/models/request"
	"hall/models/response"
	"strconv"
	"sync"
	"time"
)

type Room struct {
	sync.RWMutex
	ID            string           // 房间号
	gameRule      request.GameRule //规则
	users         map[uint]*proto.RoomUser
	GameFrame     base.GameFrame
	kickSchedules map[uint]*time.Timer
	roomDismissed bool
	gameStarted   bool
	card          base.CardBase
	notStartTimer *time.Timer
}

func (r *Room) UserEntryRoom(session *remote.Session, data *response.GameRoleInfo) *msError.Error {
	//err := r.checkTime(data.UID)
	//if err != nil {
	//	return err
	//}
	if r.roomDismissed {
		logs.Error("[game]用户%d 创建|加入房间 err=该房间已解散，无法加入", data.UID)
		return biz.GameRoomDismissedError
	}
	if r.gameStarted {
		logs.Error("[game]用户%d 创建|加入房间 err=该房间状态为已开始游戏，无法加入", data.UID)
		return biz.GameRoomStarError
	}
	if len(r.users) >= r.gameRule.MaxPlayerCount {
		logs.Error("[game]用户%d 创建|加入房间 err=房间已满", data.UID)
		return biz.GameRoomFullError
	}
	r.Lock()
	defer r.Unlock()
	//分配座位号
	//infos := r.users[data.UID].UserInfo
	//fmt.Println(infos)
	if r.users[data.UID] != nil {
		delete(r.users, data.UID)
	}
	chairID := r.getEmptyChairID(0)
	//1.给用户添加房间号
	r.users[data.UID] = proto.ToRoomUser(*data, chairID, session)
	uidStr := fmt.Sprintf("%d", data.UID)
	//2.将房间号 推送给客户端 储存当前房间号
	r.ServerMessagePush([]string{uidStr}, proto.UpdateUserInfoPush(r.ID, chairID), session)
	session.Put("roomId", r.ID)
	//3.将房间规则推送给用户
	r.ServerMessagePush([]string{uidStr}, proto.GetRoomSceneInfoPushData(&r.gameRule), session)
	//4.将游戏类型 推送给客户端 （用户进入游戏的推送）
	r.ServerMessagePush([]string{uidStr}, proto.SelfEntryRoomPush(r.gameRule.GameType), session)
	//5.将其他用户的信息推送我
	r.UserEntryRoomPush(session, data.UID)
	//6.告诉其他人 此用户进入房间了
	r.OtherUserEntryRoomPush(session, data.UID)
	//7.定时处理未准备人员
	go r.addKicksScheduleEvent(session, data.UID)
	return nil
}

// getEmptyChairID 分配座位号
func (r *Room) getEmptyChairID(chairID int) int {
	if len(r.users) == 0 {
		return 0
	}
	for _, v := range r.users {
		if v.ChairID == chairID {
			// 座位号被占用了
			chairID++
			return r.getEmptyChairID(chairID)
		} else {
			continue
		}
	}
	//var chairID int
	//chairID = r.chairID[0]
	//r.chairID = r.chairID[1:]
	//fmt.Println(r.chairID)
	return chairID
}

// ServerMessagePush 封装socket推送
func (r *Room) ServerMessagePush(users []string, data any, session *remote.Session) {
	session.Push(users, data, "ServerMessagePush")
}

// UserEntryRoomPush 将其他用户的信息推送我
func (r *Room) UserEntryRoomPush(session *remote.Session, uid uint) {
	others := make([]*proto.RoomUser, r.gameRule.MaxPlayerCount)
	for _, v := range r.users {
		others[v.ChairID] = v
		//others = append(others, v)
	}
	r.ServerMessagePush([]string{fmt.Sprintf("%d", uid)}, proto.UserEntryRoomPushData(others), session)
}

// OtherUserEntryRoomPush 告诉其他人 此用户进入房间了
func (r *Room) OtherUserEntryRoomPush(session *remote.Session, uid uint) {
	others := make([]string, 0)
	for _, v := range r.users {
		if v.UserInfo.UID != uid {
			others = append(others, fmt.Sprintf("%d", v.UserInfo.UID))
		}
	}
	user, ok := r.users[uid]
	if ok {
		r.ServerMessagePush(others, proto.OtherUserEntryRoomPushData(user), session)
	}
}

// addKicksScheduleEvent 定时处理未准备人员
func (r *Room) addKicksScheduleEvent(session *remote.Session, uid uint) {
	r.Lock()
	defer r.Unlock()
	t, ok := r.kickSchedules[uid]
	if ok {
		t.Stop()
		delete(r.kickSchedules, uid)
	}
	r.kickSchedules[uid] = time.AfterFunc(r.gameRule.WaitReady*time.Second, func() {
		logs.Info("kick 定时执行，代表 用户长时间未准备")
		// 取消定时任务
		timer, ok := r.kickSchedules[uid]
		if ok {
			timer.Stop()
		}
		delete(r.kickSchedules, uid)
		// 需要判断用户是否该踢出
		user, ok := r.users[uid]
		if ok {
			if user.UserStatus < proto.Ready {
				r.kickUser(user, session)
				// 踢出房间之后，需要判断是否可以解散房间
				if len(r.users) == 0 {
					r.DismissRoom()
				}
			}
		}
	})
}

// kickUser 从房间内删除用户
func (r *Room) kickUser(user *proto.RoomUser, session *remote.Session) {
	// 将roomId设为空
	r.ServerMessagePush([]string{fmt.Sprintf("%d", user.UserInfo.UID)}, proto.UpdateUserInfoPush("", 0), session)
	// 通知其他人离开房间
	users := make([]string, 0)
	for _, v := range r.users {
		users = append(users, fmt.Sprintf("%d", v.UserInfo.UID))
	}
	r.ServerMessagePush(users, proto.UserLeaveRoomPushData(user.ChairID), session)
	delete(r.users, user.UserInfo.UID)
}

// DismissRoom 解散房间
func (r *Room) DismissRoom() {
	r.Lock()
	defer r.Unlock()
	if r.roomDismissed {
		return
	}
	r.roomDismissed = true
	// 解散 将card当中的room信息 删除掉
	r.cancelAllScheduler()
	r.card.DismissRoom(r.ID)
}

// cancelAllScheduler 删除房间内所有定时任务
func (r *Room) cancelAllScheduler() {
	// 需要将房间所有的任务 都取消掉
	for uid, v := range r.kickSchedules {
		v.Stop()
		delete(r.kickSchedules, uid)
	}
}

// GetGameRule 获取该房间的规则
func (r *Room) GetGameRule() request.GameRule {
	return r.gameRule
}

// RoomMessageHandle 房间内操作
func (r *Room) RoomMessageHandle(session *remote.Session, uid uint, req request.RoomMessageReq, warehouseService *service.WarehouseService) {
	err := r.checkTime(uid)
	if err != nil {
		r.userLeave(uid, session)
		return
	}
	if req.Type == proto.UserReadyNotify {
		r.userReady(uid, session, req.Data.IsReady, warehouseService)
	}
	if req.Type == proto.UserLeaveRoomNotify {
		r.userLeave(uid, session)
	}
}

// userReady 用户准备或取消准备
func (r *Room) userReady(uid uint, session *remote.Session, ready bool, warehouseService *service.WarehouseService) {
	if ready { //准备
		//1.push用户的座次，修改用户的状态，取消定时任务
		user, ok := r.users[uid]
		if !ok {
			return
		}
		user.UserStatus = proto.Ready
		timer, ok := r.kickSchedules[uid]
		if ok {
			timer.Stop()
			delete(r.kickSchedules, uid)
		}
		allUsers := r.AllUsers()
		r.ServerMessagePush(allUsers, proto.UserReadyPushData(user.ChairID, ready), session)
		//2.准备好之后，判断是否需要开始游戏
		// TODO 正式开启
		if r.IsStartGame() {
			r.startGame(session, warehouseService)
		}
	} else { //取消准备
		//1.push用户的座次，修改用户的状态，取消定时任务
		user, ok := r.users[uid]
		if !ok {
			return
		}
		if user.UserStatus != proto.Ready {
			return
		}
		user.UserStatus = proto.None
		allUsers := r.AllUsers()
		r.ServerMessagePush(allUsers, proto.UserReadyPushData(user.ChairID, ready), session)
		//定时处理未准备人员
		go r.addKicksScheduleEvent(session, uid)
	}
}

// userLeave 离开房间
func (r *Room) userLeave(uid uint, session *remote.Session) {
	uidStr := strconv.Itoa(int(uid))
	user, ok := r.users[uid]
	if !ok {
		r.ServerMessagePush([]string{uidStr}, proto.UserLeaveRoomResponseData(biz.NotInRoom), session)
		return
	}
	if user.UserStatus != proto.None {
		r.ServerMessagePush([]string{uidStr}, proto.UserLeaveRoomResponseData(biz.GameCancelReadyRoom), session)
		return
	}
	r.ServerMessagePush([]string{uidStr}, proto.UserLeaveRoomResponseData(nil), session)
	// 关闭定时
	timer, ok := r.kickSchedules[uid]
	if ok {
		timer.Stop()
		delete(r.kickSchedules, uid)
	}
	r.kickUser(user, session)
	// 踢出房间之后，需要判断是否可以解散房间
	if len(r.users) == 0 {
		r.DismissRoom()
	}
}

// AllUsers 获取所有用户id
func (r *Room) AllUsers() []string {
	users := make([]string, 0)
	for _, v := range r.users {
		users = append(users, fmt.Sprintf("%d", v.UserInfo.UID))
	}
	return users
}

// IsStartGame 验证是否具备开始游戏的条件
func (r *Room) IsStartGame() bool {
	// 房间内准备的人数 已经大于等于 最小开始游戏人数
	userReadyCount := 0
	for _, v := range r.users {
		if v.UserStatus == proto.Ready {
			userReadyCount++
		}
	}
	if r.gameRule.GameType == 1 {
		if len(r.users) == userReadyCount && userReadyCount >= r.gameRule.MaxPlayerCount {
			return true
		}
	}
	if len(r.users) == userReadyCount && userReadyCount >= r.gameRule.MinPlayerCount {
		return true
	}
	return false
}

// startGame 开始游戏 修改用户状态
func (r *Room) startGame(session *remote.Session, warehouseService *service.WarehouseService) {
	if r.gameStarted {
		return
	}
	r.gameStarted = true
	r.notStartTimer.Stop()
	for _, v := range r.users {
		v.UserStatus = proto.Playing
	}
	r.GameFrame.StartGame(session, warehouseService)
}

func (r *Room) GetUsers() map[uint]*proto.RoomUser {
	r.RLock()
	defer r.RUnlock()
	return r.users
}

func (r *Room) GameMessageHandle(session *remote.Session, uid uint, msg []byte) {
	// 需要游戏去处理具体的消息
	user, ok := r.users[uid]
	if !ok {
		return
	}
	r.GameFrame.GameMessageHandle(user, session, msg)
}

func (r *Room) GetGameStatus() bool {
	return r.gameStarted
}

func (r *Room) GetRoomID() string {
	return r.ID
}

func (r *Room) checkTime(uid uint) *msError.Error {
	dates := utils.TimeOrDateAsYear(0, "", "Y-m-d")
	startDate := fmt.Sprintf("%v %v", dates, r.gameRule.StartTime)
	startTime := utils.DateAsTime(startDate)
	endDate := fmt.Sprintf("%v %v", dates, r.gameRule.EndTime)
	endTime := utils.DateAsTime(endDate)
	if endTime <= startTime {
		endTime += 86400
	}
	t := time.Now().Unix()
	if t < startTime || t > endTime {
		logs.Error("[Game]用户%d FleeBetting err:当前时间不在可进入时间段", uid)
		return biz.GameNotEnabledError
	}
	return nil
}

func (r *Room) notStartTimerHandle() {
	timer := r.notStartTimer
	if timer != nil {
		timer.Stop()
	}
	r.notStartTimer = time.AfterFunc(time.Second*time.Duration(5*60), func() {
		r.notStartTimer.Stop()
		if r.gameStarted {
			return
		}
		for _, v := range r.users {
			r.kickUser(v, v.Session)
		}
		r.DismissRoom()
	})
}

func NewRoom(id string, rule request.GameRule, gameID uint, m base.CardBase, gameService *service.GameService) *Room {
	r := &Room{
		ID:            id,
		gameRule:      rule,
		users:         make(map[uint]*proto.RoomUser),
		kickSchedules: make(map[uint]*time.Timer),
		card:          m,
	}
	if gameID == 1 {
		r.GameFrame = gameCard.NewGameFrame(rule, r, gameService)
	}
	go r.notStartTimerHandle()
	return r
}
